/** @file
  Copyright (C) 2016 - 2018, The HermitCrabs Lab. All rights reserved.

  All rights reserved.

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/

#include <Uefi.h>

#include <Protocol/DevicePathToText.h>
#include <Protocol/SimpleFileSystem.h>

#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcGuardLib.h>
#include <Library/OcStringLib.h>
#include <Library/OcDevicePathLib.h>
#include <Library/UefiBootServicesTableLib.h>

EFI_DEVICE_PATH_PROTOCOL *
AppendFileNameDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN CHAR16                    *FileName
  )
{
  EFI_DEVICE_PATH_PROTOCOL *AppendedDevicePath;

  FILEPATH_DEVICE_PATH     *FilePathNode;
  EFI_DEVICE_PATH_PROTOCOL *DevicePathEndNode;
  UINTN                    FileNameSize;
  UINTN                    FileDevicePathNodeSize;

  AppendedDevicePath = NULL;

  if (DevicePath != NULL && FileName != NULL) {
    FileNameSize           = StrSize (FileName);
    FileDevicePathNodeSize = (FileNameSize + SIZE_OF_FILEPATH_DEVICE_PATH + END_DEVICE_PATH_LENGTH);
    FilePathNode           = AllocateZeroPool (FileDevicePathNodeSize);

    if (FilePathNode != NULL) {
      FilePathNode->Header.Type    = MEDIA_DEVICE_PATH;
      FilePathNode->Header.SubType = MEDIA_FILEPATH_DP;

      SetDevicePathNodeLength (&FilePathNode->Header, FileNameSize + SIZE_OF_FILEPATH_DEVICE_PATH);

      CopyMem (FilePathNode->PathName, FileName, FileNameSize);

      DevicePathEndNode = NextDevicePathNode (&FilePathNode->Header);

      SetDevicePathEndNode (DevicePathEndNode);

      AppendedDevicePath = AppendDevicePath (DevicePath, (EFI_DEVICE_PATH_PROTOCOL *)FilePathNode);

      FreePool (FilePathNode);
    }
  }

  return AppendedDevicePath;
}

EFI_DEVICE_PATH_PROTOCOL *
FindDevicePathNodeWithType (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN UINT8                     Type,
  IN UINT8                     SubType OPTIONAL
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePathNode;

  DevicePathNode = NULL;

  while (!IsDevicePathEnd (DevicePath)) {
    if ((DevicePathType (DevicePath) == Type)
     && ((SubType == 0) || (DevicePathSubType (DevicePath) == SubType))) {
      DevicePathNode = DevicePath;

      break;
    }

    DevicePath = NextDevicePathNode (DevicePath);
  }

  return DevicePathNode;
}

EFI_DEVICE_PATH_PROTOCOL *
AbsoluteDevicePath (
  IN EFI_HANDLE                Handle,
  IN EFI_DEVICE_PATH_PROTOCOL  *RelativePath OPTIONAL
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *HandlePath;
  EFI_DEVICE_PATH_PROTOCOL  *NewPath;

  HandlePath = DevicePathFromHandle (Handle);
  if (HandlePath == NULL) {
    return NULL;
  }

  if (RelativePath == NULL) {
    return DuplicateDevicePath (HandlePath);
  }

  NewPath = AppendDevicePath (HandlePath, RelativePath);

  if (NewPath == NULL) {
    return DuplicateDevicePath (HandlePath);
  }

  return NewPath;
}

EFI_DEVICE_PATH_PROTOCOL *
TrailedBooterDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathWalker;
  EFI_DEVICE_PATH_PROTOCOL  *NewDevicePath;
  FILEPATH_DEVICE_PATH      *FilePath;
  FILEPATH_DEVICE_PATH      *NewFilePath;
  UINTN                     Length;
  UINTN                     Size;

  DevicePathWalker = DevicePath;

  while (!IsDevicePathEnd (DevicePathWalker)) {
    if ((DevicePathType (DevicePathWalker) == MEDIA_DEVICE_PATH)
     && (DevicePathSubType (DevicePathWalker) == MEDIA_FILEPATH_DP)
     && IsDevicePathEnd (NextDevicePathNode (DevicePathWalker))) {
      FilePath = (FILEPATH_DEVICE_PATH *) DevicePathWalker;
      Length   = OcFileDevicePathNameLen (FilePath);
      if (Length > 0) {
        if (FilePath->PathName[Length - 1] == L'\\') {
          //
          // Already appended, good. It should never be true with Apple entries though.
          //
          return NULL;
        } else if (Length > 4 &&                      (FilePath->PathName[Length - 4] != '.'
          || (FilePath->PathName[Length - 3] != 'e' && FilePath->PathName[Length - 3] != 'E')
          || (FilePath->PathName[Length - 2] != 'f' && FilePath->PathName[Length - 2] != 'F')
          || (FilePath->PathName[Length - 1] != 'i' && FilePath->PathName[Length - 1] != 'I'))) {
          //
          // Found! We should have gotten something like:
          // PciRoot(0x0)/Pci(...)/Pci(...)/Sata(...)/HD(...)/\com.apple.recovery.boot
          //

          Size          = GetDevicePathSize (DevicePath);
          NewDevicePath = (EFI_DEVICE_PATH_PROTOCOL *) AllocatePool (Size + sizeof (CHAR16));
          if (NewDevicePath == NULL) {
            //
            // Allocation failure, just ignore.
            //
            return NULL;
          }
          //
          // Strip the string termination and DP end node, which will get re-set
          //
          CopyMem (NewDevicePath, DevicePath, Size - sizeof (CHAR16) - END_DEVICE_PATH_LENGTH);
          NewFilePath = (FILEPATH_DEVICE_PATH *) ((UINT8 *)DevicePathWalker - (UINT8 *)DevicePath + (UINT8 *)NewDevicePath);
          Size        = DevicePathNodeLength (DevicePathWalker) + sizeof (CHAR16);
          SetDevicePathNodeLength (NewFilePath, Size);
          NewFilePath->PathName[Length]   = L'\\';
          NewFilePath->PathName[Length+1] = L'\0';
          SetDevicePathEndNode ((UINT8 *) NewFilePath + Size);
          return NewDevicePath;
        }
      }
    }

    DevicePathWalker = NextDevicePathNode (DevicePathWalker);
  }

  //
  // Has .efi suffix or unsupported format.
  //
  return NULL;
}

INTN
OcFixAppleBootDevicePath (
  IN OUT EFI_DEVICE_PATH_PROTOCOL  **DevicePath
  )
{
  INTN                     Result;

  EFI_DEVICE_PATH_PROTOCOL *OriginalDevPath;

  EFI_DEV_PATH_PTR         InvalidNode;
  UINT8                    NodeType;
  UINT8                    NodeSubType;
  UINTN                    NodeSize;

  EFI_DEVICE_PATH_PROTOCOL *RemainingDevPath;
  EFI_HANDLE               Device;

  ASSERT (DevicePath != NULL);
  ASSERT (*DevicePath != NULL);
  ASSERT (IsDevicePathValid (*DevicePath, 0));
  //
  // CAUTION: When adding new fixes, ensure short-form device paths are not
  //          modified and success is returned.
  //
  OriginalDevPath = *DevicePath;
  //
  // Failure will be returned explicitly within the loop.  If this loop is run
  // only once, it means the Device Path had already been valid.  Hence, Result
  // will be 0 on termination.  Shall any switch-case continue, which it needs
  // to in order to patch subsequent nodes, Result will be incremented.
  //
  Result = -1;
  while (TRUE) {
    if (Result != MAX_INTN) {
      ++Result;
    }

    RemainingDevPath = OriginalDevPath;
    gBS->LocateDevicePath (
           &gEfiDevicePathProtocolGuid,
           &RemainingDevPath,
           &Device
           );

    *DevicePath = RemainingDevPath;

    InvalidNode.DevPath = RemainingDevPath;
    NodeType    = DevicePathType (InvalidNode.DevPath);
    NodeSubType = DevicePathSubType (InvalidNode.DevPath);

    if (NodeType == MESSAGING_DEVICE_PATH) {
      switch (NodeSubType) {
        case MSG_SATA_DP:
        {
          if (InvalidNode.Sata->PortMultiplierPortNumber != 0xFFFF) {
            //
            // Must be set to 0xFFFF if the device is directly connected to the
            // HBA. This rule has been established by UEFI 2.5 via an Erratum
            // and has not been followed by Apple thus far.
            // Reference: AppleACPIPlatform.kext,
            //            appendSATADevicePathNodeForIOMedia
            //
            InvalidNode.Sata->PortMultiplierPortNumber = 0xFFFF;
            continue;
          }

          return -1;
        }

        case MSG_SASEX_DP:
        {
          STATIC_ASSERT (
            (sizeof (SASEX_DEVICE_PATH) != sizeof (NVME_NAMESPACE_DEVICE_PATH)),
            "SasEx and NVMe DPs must differ in size for fixing to be accurate."
            );
          //
          // Apple uses SubType 0x16 (SasEx) for NVMe, while the UEFI
          // Specification defines it as SubType 0x17. The structures are
          // identical.
          // Reference: AppleACPIPlatform.kext,
          //            appendNVMeDevicePathNodeForIOMedia
          //
          NodeSize = DevicePathNodeLength (InvalidNode.DevPath);
          if (NodeSize == sizeof (NVME_NAMESPACE_DEVICE_PATH)) {
            InvalidNode.SasEx->Header.SubType = MSG_NVME_NAMESPACE_DP;
            continue;
          }

          return -1;
        }

        case MSG_NVME_NAMESPACE_DP:
        {
          //
          // Apple MacPro5,1 includes NVMe driver, however, it contains a typo in MSG_SASEX_DP.
          // Instead of 0x16 aka 22 (SasEx) it uses 0x22 aka 34 (Unspecified).
          // Here we replace it with the "right" value.
          // Reference: https://forums.macrumors.com/posts/28169441.
          //
          InvalidNode.NvmeNamespace->Header.SubType = 0x22;
          continue;
        }

        default:
        {
          break;
        }
      }
    } else if (NodeType == ACPI_DEVICE_PATH) {
      //
      // Apple uses PciRoot (EISA 0x0A03) nodes while some firmwares might use
      // PcieRoot (EISA 0x0A08).
      //
      switch (NodeSubType) {
        case ACPI_DP:
        {
          if (EISA_ID_TO_NUM (InvalidNode.Acpi->HID) == 0x0A03) {
            //
            // In some firmwares UIDs for PciRoot do not match between ACPI tables and UEFI
            // UEFI Device Paths. The former contain 0x00, 0x40, 0x80, 0xC0 values, while
            // the latter have ascending numbers.
            // Reference: https://github.com/acidanthera/bugtracker/issues/664.
            //
            if (InvalidNode.Acpi->UID == 0x40) {
              InvalidNode.Acpi->UID = 1;
              continue;
            } else if (InvalidNode.Acpi->UID == 0x80) {
              InvalidNode.Acpi->UID = 2;
              continue;
            } else if (InvalidNode.Acpi->UID == 0xC0) {
              InvalidNode.Acpi->UID = 3;
              continue;
            }

            InvalidNode.Acpi->HID = BitFieldWrite32 (
                                      InvalidNode.Acpi->HID,
                                      16,
                                      31,
                                      0x0A08
                                      );
            continue;
          }

          return -1;
        }

        case ACPI_EXTENDED_DP:
        {
          if (EISA_ID_TO_NUM (InvalidNode.ExtendedAcpi->HID) == 0x0A03) {
            InvalidNode.ExtendedAcpi->HID = BitFieldWrite32 (
                                              InvalidNode.ExtendedAcpi->HID,
                                              16,
                                              31,
                                              0x0A08
                                              );
            continue;
          }
          
          if (((EISA_ID_TO_NUM (InvalidNode.ExtendedAcpi->CID) == 0x0A03)
            && (EISA_ID_TO_NUM (InvalidNode.ExtendedAcpi->HID) != 0x0A08))) {
            InvalidNode.ExtendedAcpi->CID = BitFieldWrite32 (
                                              InvalidNode.ExtendedAcpi->CID,
                                              16,
                                              31,
                                              0x0A08
                                              );
            continue;
          }

          return -1;
        }

        default:
        {
          break;
        }
      }
    }

    return Result;
  }
}

STATIC
BOOLEAN
InternalFileDevicePathsEqualClipBottom (
  IN     CONST FILEPATH_DEVICE_PATH  *FilePath,
  IN OUT UINTN                       *FilePathLength,
  IN OUT UINTN                       *ClipIndex
  )
{
  UINTN Index;

  ASSERT (FilePathLength != NULL);
  ASSERT (*FilePathLength != 0);
  ASSERT (FilePath != NULL);
  ASSERT (ClipIndex != NULL);

  Index = *ClipIndex;
  if (FilePath->PathName[Index] == L'\\') {
    *ClipIndex = Index + 1;
    --(*FilePathLength);
    return TRUE;
  }

  return FALSE;
}

STATIC
UINTN
InternalFileDevicePathsEqualClipNode (
  IN  FILEPATH_DEVICE_PATH  **FilePath,
  OUT UINTN                 *ClipIndex
  )
{
  EFI_DEV_PATH_PTR         DevPath;
  UINTN                    Index;

  UINTN                    Length;
  EFI_DEVICE_PATH_PROTOCOL *NextNode;

  ASSERT (FilePath != NULL);
  ASSERT (ClipIndex != NULL);
  //
  // It is unlikely to be encountered, but empty nodes are not forbidden.
  //
  for (
    Length = 0, NextNode = &(*FilePath)->Header;
    Length == 0;
    NextNode = NextDevicePathNode (DevPath.DevPath)
    ) {
    DevPath.DevPath = NextNode;

    if ((DevicePathType (DevPath.DevPath) != MEDIA_DEVICE_PATH)
     || (DevicePathSubType (DevPath.DevPath) != MEDIA_FILEPATH_DP)) {
      return 0;
    }

    Length = OcFileDevicePathNameLen (DevPath.FilePath);
    if (Length > 0) {
      Index = 0;
      InternalFileDevicePathsEqualClipBottom (DevPath.FilePath, &Length, &Index);
      if ((Length > 0)
       && DevPath.FilePath->PathName[Index + Length - 1] == L'\\') {
        --Length;
      }

      *ClipIndex = Index;
    }
  }

  *FilePath = DevPath.FilePath;
  return Length;
}

STATIC
UINTN
InternalFileDevicePathsEqualClipNextNode (
  IN  FILEPATH_DEVICE_PATH  **FilePath,
  OUT UINTN                 *ClipIndex
  )
{
  ASSERT (FilePath != NULL);
  ASSERT (ClipIndex != NULL);

  *FilePath = (FILEPATH_DEVICE_PATH *)NextDevicePathNode (*FilePath);
  return InternalFileDevicePathsEqualClipNode (FilePath, ClipIndex);
}

BOOLEAN
InternalFileDevicePathsEqualWorker (
  IN FILEPATH_DEVICE_PATH  **FilePath1,
  IN FILEPATH_DEVICE_PATH  **FilePath2
  )
{
  UINTN   Clip1Index;
  UINTN   Clip2Index;
  UINTN   Len1;
  UINTN   Len2;
  UINTN   CurrentLen;
  UINTN   Index;
  CHAR16  Char1;
  CHAR16  Char2;
  BOOLEAN Result;

  ASSERT (FilePath1 != NULL);
  ASSERT (*FilePath1 != NULL);
  ASSERT (FilePath2 != NULL);
  ASSERT (*FilePath2 != NULL);

  ASSERT (IsDevicePathValid (&(*FilePath1)->Header, 0));
  ASSERT (IsDevicePathValid (&(*FilePath2)->Header, 0));

  Len1 = InternalFileDevicePathsEqualClipNode (FilePath1, &Clip1Index);
  Len2 = InternalFileDevicePathsEqualClipNode (FilePath2, &Clip2Index);

  do {
    if ((Len1 == 0) && (Len2 == 0)) {
      return TRUE;
    }

    CurrentLen = MIN (Len1, Len2);
    if (CurrentLen == 0) {
      return FALSE;
    }
    //
    // FIXME: Discuss case sensitivity.  For UEFI FAT, case insensitivity is
    //        guaranteed.
    //
    for (Index = 0; Index < CurrentLen; ++Index) {
      Char1 = CharToUpper ((*FilePath1)->PathName[Clip1Index + Index]);
      Char2 = CharToUpper ((*FilePath2)->PathName[Clip2Index + Index]);
      if (Char1 != Char2) {
        return FALSE;
      }
    }

    if (Len1 == Len2) {
      Len1 = InternalFileDevicePathsEqualClipNextNode (FilePath1, &Clip1Index);
      Len2 = InternalFileDevicePathsEqualClipNextNode (FilePath2, &Clip2Index);
    } else if (Len1 < Len2) {
      Len1 = InternalFileDevicePathsEqualClipNextNode (FilePath1, &Clip1Index);
      if (Len1 == 0) {
        return FALSE;
      }

      Len2       -= CurrentLen;
      Clip2Index += CurrentLen;
      //
      // Switching to the next node for the other Device Path implies a path
      // separator.  Verify we hit such in the currently walked path too.
      //
      Result = InternalFileDevicePathsEqualClipBottom (*FilePath2, &Len2, &Clip2Index);
      if (!Result) {
        return FALSE;
      }
    } else {
      Len2 = InternalFileDevicePathsEqualClipNextNode (FilePath2, &Clip2Index);
      if (Len2 == 0) {
        return FALSE;
      }

      Len1       -= CurrentLen;
      Clip1Index += CurrentLen;
      //
      // Switching to the next node for the other Device Path implies a path
      // separator.  Verify we hit such in the currently walked path too.
      //
      Result = InternalFileDevicePathsEqualClipBottom (*FilePath1, &Len1, &Clip1Index);
      if (!Result) {
        return FALSE;
      }
    }
  } while (TRUE);
}

/**
  Check whether File Device Paths are equal.

  @param[in] FilePath1  The first device path protocol to compare.
  @param[in] FilePath2  The second device path protocol to compare.

  @retval TRUE         The device paths matched
  @retval FALSE        The device paths were different
**/
BOOLEAN
FileDevicePathsEqual (
  IN FILEPATH_DEVICE_PATH  *FilePath1,
  IN FILEPATH_DEVICE_PATH  *FilePath2
  )
{
  ASSERT (FilePath1 != NULL);
  ASSERT (FilePath2 != NULL);

  return InternalFileDevicePathsEqualWorker (&FilePath1, &FilePath2);
}

STATIC
BOOLEAN
InternalDevicePathCmpWorker (
  IN EFI_DEVICE_PATH_PROTOCOL  *ParentPath,
  IN EFI_DEVICE_PATH_PROTOCOL  *ChildPath,
  IN BOOLEAN                   CheckChild
  )
{
  BOOLEAN          Result;
  INTN             CmpResult;

  EFI_DEV_PATH_PTR ChildPathPtr;
  EFI_DEV_PATH_PTR ParentPathPtr;

  UINT8            NodeType;
  UINT8            NodeSubType;
  UINTN            NodeSize;

  ASSERT (ParentPath != NULL);
  ASSERT (IsDevicePathValid (ParentPath, 0));
  ASSERT (ChildPath != NULL);
  ASSERT (IsDevicePathValid (ChildPath, 0));

  ParentPathPtr.DevPath = ParentPath;
  ChildPathPtr.DevPath  = ChildPath;

  while (TRUE) {
    NodeType    = DevicePathType (ChildPathPtr.DevPath);
    NodeSubType = DevicePathSubType (ChildPathPtr.DevPath);

    if (NodeType == END_DEVICE_PATH_TYPE) {
      //
      // We only support single-instance Device Paths.
      //
      ASSERT (NodeSubType == END_ENTIRE_DEVICE_PATH_SUBTYPE);
      return (CheckChild
          || (DevicePathType (ParentPathPtr.DevPath) == END_DEVICE_PATH_TYPE));
    }

    if ((DevicePathType (ParentPathPtr.DevPath) != NodeType)
     || (DevicePathSubType (ParentPathPtr.DevPath) != NodeSubType)) {
      return FALSE;
    }

    if ((NodeType == MEDIA_DEVICE_PATH)
     && (NodeSubType == MEDIA_FILEPATH_DP)) {
      //
      // File Paths need special consideration for prepended and appended
      // terminators, as well as multiple nodes.
      //
      Result = InternalFileDevicePathsEqualWorker (
                 &ParentPathPtr.FilePath,
                 &ChildPathPtr.FilePath
                 );
      if (!Result) {
        return FALSE;
      }
      //
      // InternalFileDevicePathsEqualWorker advances the nodes.
      //
    } else {
      NodeSize = DevicePathNodeLength (ChildPathPtr.DevPath);
      if (DevicePathNodeLength (ParentPathPtr.DevPath) != NodeSize) {
        return FALSE;
      }

      STATIC_ASSERT (
        (sizeof (*ChildPathPtr.DevPath) == 4),
        "The Device Path comparison logic depends on the entire header being checked"
        );

      CmpResult = CompareMem (
                    (ChildPathPtr.DevPath + 1),
                    (ParentPathPtr.DevPath + 1),
                    (NodeSize - sizeof (*ChildPathPtr.DevPath))
                    );
      if (CmpResult != 0) {
        return FALSE;
      }

      ParentPathPtr.DevPath = NextDevicePathNode (ParentPathPtr.DevPath);
      ChildPathPtr.DevPath  = NextDevicePathNode (ChildPathPtr.DevPath);
    }
  }
}

BOOLEAN
EFIAPI
IsDevicePathEqual (
  IN  EFI_DEVICE_PATH_PROTOCOL      *DevicePath1,
  IN  EFI_DEVICE_PATH_PROTOCOL      *DevicePath2
  )
{
  return InternalDevicePathCmpWorker (DevicePath1, DevicePath2, FALSE);
}

BOOLEAN
EFIAPI
IsDevicePathChild (
  IN  EFI_DEVICE_PATH_PROTOCOL      *ParentPath,
  IN  EFI_DEVICE_PATH_PROTOCOL      *ChildPath
  )
{
  return InternalDevicePathCmpWorker (ParentPath, ChildPath, TRUE);
}

UINTN
OcFileDevicePathNameSize (
  IN CONST FILEPATH_DEVICE_PATH  *FilePath
  )
{
  ASSERT (FilePath != NULL);
  ASSERT (IsDevicePathValid (&FilePath->Header, 0));
  return (OcFileDevicePathNameLen (FilePath) + 1) * sizeof (*FilePath->PathName);
}

UINTN
OcFileDevicePathNameLen (
  IN CONST FILEPATH_DEVICE_PATH  *FilePath
  )
{
  UINTN Size;
  UINTN Len;

  ASSERT (FilePath != NULL);
  ASSERT (IsDevicePathValid (&FilePath->Header, 0));

  Size = DevicePathNodeLength (FilePath) - SIZE_OF_FILEPATH_DEVICE_PATH;
  //
  // Account for more than one termination character.
  //
  Len = (Size / sizeof (*FilePath->PathName)) - 1;
  while (Len > 0 && FilePath->PathName[Len - 1] == L'\0') {
    --Len;
  }

  return Len;
}

/**
  Retrieve the size of the full file path described by DevicePath.

  @param[in] DevicePath  The Device Path to inspect.

  @returns   The size of the full file path.
  @retval 0  DevicePath does not start with a File Path node or contains
             non-terminating nodes that are not File Path nodes.

**/
UINTN
OcFileDevicePathFullNameSize (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  UINTN                      PathSize;
  CONST FILEPATH_DEVICE_PATH *FilePath;

  ASSERT (DevicePath != NULL);
  ASSERT (IsDevicePathValid (DevicePath, 0));

  PathSize = 1;
  do {
    //
    // On the first iteration, this ensures the path is not immediately
    // terminated.
    //
    if (DevicePath->Type    != MEDIA_DEVICE_PATH
     || DevicePath->SubType != MEDIA_FILEPATH_DP) {
      return 0;
    }

    FilePath  = (FILEPATH_DEVICE_PATH *)DevicePath;
    PathSize += OcFileDevicePathNameLen (FilePath);

    DevicePath = NextDevicePathNode (DevicePath);
  } while (!IsDevicePathEnd (DevicePath));
  return PathSize * sizeof (*FilePath->PathName);
}

/**
  Retrieve the full file path described by FilePath.
  The caller is expected to call OcFileDevicePathFullNameSize() or ensure its
  guarantees are met.

  @param[out] PathName      On output, the full file path of FilePath.
  @param[in]  FilePath      The File Device Path to inspect.
  @param[in]  PathNameSize  The size, in bytes, of PathnName.  Must equal the
                            actual fill file path size.

**/
VOID
OcFileDevicePathFullName (
  OUT CHAR16                      *PathName,
  IN  CONST FILEPATH_DEVICE_PATH  *FilePath,
  IN  UINTN                       PathNameSize
  )
{
  UINTN                          PathLen;

  ASSERT (PathName != NULL);
  ASSERT (FilePath != NULL);
  ASSERT (IsDevicePathValid (&FilePath->Header, 0));
  ASSERT (PathNameSize == OcFileDevicePathFullNameSize (&FilePath->Header));

  do {
    PathLen = OcFileDevicePathNameLen (FilePath);
    CopyMem (
      PathName,
      FilePath->PathName,
      PathLen * sizeof (*FilePath->PathName)
      );
    PathName += PathLen;

    FilePath = (CONST FILEPATH_DEVICE_PATH *)NextDevicePathNode (FilePath);
  } while (!IsDevicePathEnd (FilePath));
  *PathName = CHAR_NULL;
}

EFI_DEVICE_PATH_PROTOCOL *
OcAppendDevicePathInstanceDedupe (
  IN EFI_DEVICE_PATH_PROTOCOL        *DevicePath OPTIONAL,
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePathInstance
  )
{
  INTN                           CmpResult;

  EFI_DEVICE_PATH_PROTOCOL       *DevPathWalker;
  EFI_DEVICE_PATH_PROTOCOL       *CurrentInstance;

  UINTN                          AppendInstanceSize;
  UINTN                          CurrentInstanceSize;

  ASSERT (DevicePathInstance != NULL);

  if (DevicePath != NULL) {
    AppendInstanceSize = GetDevicePathSize (DevicePathInstance);
    DevPathWalker = DevicePath;

    while (TRUE) {
      CurrentInstance = GetNextDevicePathInstance (
        &DevPathWalker,
        &CurrentInstanceSize
        );
      if (CurrentInstance == NULL) {
        break;
      }

      if (CurrentInstanceSize != AppendInstanceSize) {
        FreePool (CurrentInstance);
        continue;
      }

      CmpResult = CompareMem (
        CurrentInstance,
        DevicePathInstance,
        CurrentInstanceSize
        );

      FreePool (CurrentInstance);

      if (CmpResult == 0) {
        return DuplicateDevicePath (DevicePath);
      }
    }
  }

  return AppendDevicePathInstance (DevicePath, DevicePathInstance);
}

UINTN
OcGetNumDevicePathInstances (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  UINTN NumInstances;

  NumInstances = 1;

  while (!IsDevicePathEnd (DevicePath)) {
    if (IsDevicePathEndInstance (DevicePath)) {
      ++NumInstances;
    }

    DevicePath = NextDevicePathNode (DevicePath);
  }

  return NumInstances;
}
